use serde::Serialize;
use std::error::Error as StdErr;
use thiserror::Error;

pub type SerialErr = tokio_serial::Error;
pub type IoErr = std::io::Error;

#[derive(Debug, Error)]
pub enum AppErr {
    #[error("io:{0}")]
    Io(#[from] IoErr),

    #[error("{0}")]
    Static(&'static str),

    #[error("{0}")]
    Dyn(String),

    #[error("serial:{0}")]
    Serial(#[from] SerialErr),
}

impl Serialize for AppErr {
    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: serde::Serializer,
    {
        serializer.serialize_str(&self.to_string())
    }
}

#[inline]
pub fn error_of<T>(msg: &'static str) -> Result<T, AppErr> {
    Err(AppErr::Static(msg))
}

pub trait AppErrMap<T> {
    fn map_app(self) -> Result<T, AppErr>;

    fn map_static(self, msg: &'static str) -> Result<T, AppErr>;

    fn print_if_err(self);
}

impl<E: StdErr, T> AppErrMap<T> for Result<T, E> {
    fn print_if_err(self) {
        match self {
            Ok(_) => {}
            Err(e) => {
                eprintln!("err:{}", e);
            }
        }
    }

    #[inline]
    fn map_app(self) -> Result<T, AppErr> {
        match self {
            Ok(v) => Ok(v),
            Err(e) => Err(AppErr::Dyn(e.to_string())),
        }
    }

    #[inline]
    fn map_static(self, msg: &'static str) -> Result<T, AppErr> {
        match self {
            Ok(v) => Ok(v),
            Err(_) => Err(AppErr::Static(msg)),
        }
    }
}
